// LoadLibraryExample.cpp : Defines the entry point for the console application.

#include "windows.h"
#include <iostream>
#include <direct.h>

#include <iostream>
#include <fstream>

#include "..\pasmo.h"

#ifdef __GNUC__
	#include <stdio.h> //printf, Mingw
#endif // __GNUC__
using namespace std;

static bool CheckRef(const char* i_refFileName, char* i_binBytes, int i_codeSize);
static void printOK();
static void printError();
static void printError(const char* i_string);
static void SaveConsoleInfo();

static CONSOLE_SCREEN_BUFFER_INFO infoCon;

int main(int argc, char* * argv)
{
    typedef int(*GW)(COMPILE_IN* in_CompileArgs, COMPILED_INFO* out_Compiled);

    char testFile[999];
    char AppDir[999];

    COMPILE_IN* inCompile = new COMPILE_IN();
    COMPILED_INFO* compiled_info = new COMPILED_INFO();

	int retCode = 1; // 0  = OK

#ifdef __GNUC__
	HMODULE hMod = LoadLibrary("..\\bin\\Pasmo2.dll");
#else
	HMODULE hMod = LoadLibrary(L"..\\bin\\Pasmo2.dll");
#endif
	if (NULL == hMod)
	{
		printf("LoadLibrary failed\n");
		printf("Error: %ld\n", GetLastError()); //error codes: https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx
		return 1;
	}
	GW compileProc = (GW)GetProcAddress(hMod, "compile");

    //save console colors
    SaveConsoleInfo();

    //print dll version
    /*inCompile->CompileMode = 0;//version
    retCode = compileProc(inCompile, compiled_info);
    printf("Version: ");
    if (!retCode)
    {
        printf("%s", compiled_info->czCompiled);
    }
    else
    {
        printError();
        return 1;
    }
    //print dll architecture type(x86 or x64), but only x86 is allowed
    inCompile->CompileMode = 1; //machine
    retCode = compileProc(inCompile, compiled_info);
    printf(", compiler: ");
    if (!retCode && !strcmp(compiled_info->czCompiled, "x86"))
    {
        printf("%s\n\n", compiled_info->czCompiled);
    }
    else
    {
        printError();
        return 1;
    }*/

    _getcwd(AppDir, 999);

    std::string SOURCE_CODE_DEFAULT = "org 40000\n"
                                      "xor a\n" \
                                      "ld hl, 16384\n" \
                                      "inc a\n" \
                                      "ld(hl), a\n" \
                                      "ret";

    //Test1: Compile "light" assembler source code
	printf("Test 1: ");
    inCompile->chrSourceCode = (char*)SOURCE_CODE_DEFAULT.c_str();
    inCompile->CompileMode = 3; //BIN
    retCode = compileProc(inCompile, compiled_info);
    if (compiled_info->iCompiledSize != 7 || retCode != 0)
	{
        printError(compiled_info->czErrMessage);
		return 1;
	}
	else
	{
        _getcwd(testFile, 999);
        strcat(testFile, "\\Test files\\test1.ref");
        if (CheckRef(testFile, compiled_info->czCompiled, compiled_info->iCompiledSize))
            printOK();
        else
        {
            printError("ERROR: reference mismatch!");
            return 1;
        }
	}

	//Test2
    printf("Test 2: ");
    _getcwd(testFile, 999);
    strcat(testFile, "\\Test files\\test_error_line.asm");
    inCompile->chrFileIn = testFile;
    inCompile->CompileMode = 4; //BIN_FILE

    retCode = compileProc(inCompile, compiled_info);
	if (retCode != 0)
	{
        if (compiled_info->iErrFileLine != 3)
		{
			char czMessage[64];
            sprintf(czMessage, "ERROR: incorrect line number %d. Must be equal to 3.", compiled_info->iErrFileLine);
			printError(czMessage);
            printError(compiled_info->czErrMessage); return 1;
		}
		else
			printOK();
	}
	else
	{
		printError();
		return 1;
	}

    //Test3<suppressed>

    //Test4
    delete compiled_info;
    compiled_info = new COMPILED_INFO();

    printf("Test 4: ");
    _getcwd(testFile, 999);
    strcat(testFile, "\\test4.asm");
    inCompile->chrFileIn = testFile;

    retCode = compileProc(inCompile, compiled_info);
    if (retCode != 0)
    {
        if (compiled_info->iErrFileLine == 2 && !strcmp(compiled_info->czErrMessage, "Unexpected '0100' used as instruction\n"))
            printOK();
        else
            printError();
    }
    else
        printError();

    //Test5
    delete compiled_info;
    compiled_info = new COMPILED_INFO();

    _getcwd(testFile, 999);
    strcat(testFile, "\\test5_comments.asm");
    printf("Test 5: ");
    inCompile->chrFileIn = testFile;

    retCode = compileProc(inCompile, compiled_info);
    if (retCode == 0)
    {
        if (CheckRef("test5_comments.ref", compiled_info->czCompiled, compiled_info->iCompiledSize))
            printOK();
        else
            printError("ERROR: reference mismatch!");
    }
    else
        printError();

    //Test6(2 steps): labels test, 2 errors with different error message
    delete compiled_info;
    compiled_info = new COMPILED_INFO();

    _getcwd(testFile, 999);
    strcat(testFile, "\\test6_1_error_message_test.asm");
    printf("Test 6: ");
    inCompile->chrFileIn = testFile;

    retCode = compileProc(inCompile, compiled_info);
    if (retCode != 1)
    {
        printError(compiled_info->czErrMessage);
        return 1;
    }
    else
    {
        if (!CheckRef("test6_1_error_message_test.ref", compiled_info->czErrMessage, -1))
        {
            printError("ERROR: reference mismatch(step 1)!");
            return 1;
        }
    }
    _getcwd(testFile, 999);
    strcat(testFile, "\\test6_2_error_message_test.asm");
    inCompile->chrFileIn = testFile;
    retCode = compileProc(inCompile, compiled_info);
    if (retCode != 1)
    {
        printError();
        return 1;
    }
    else
    {
        if (CheckRef("test6_2_error_message_test.ref", compiled_info->czErrMessage, -1))
            printOK();
        else
            printError("ERROR: reference mismatch(step 2)!");
    }

    //Test7: EQU
    delete compiled_info;
    compiled_info = new COMPILED_INFO();

    _getcwd(testFile, 999);
    strcat(testFile, "\\test7_label_equ.asm");
    printf("Test 7: ");
    inCompile->chrFileIn = testFile;

    retCode = compileProc(inCompile, compiled_info);
    if (retCode == 0)
    {
        if (CheckRef("test7_label_equ.ref", compiled_info->czCompiled, compiled_info->iCompiledSize))
            printOK();
        else
            printError("ERROR: reference mismatch!");
    }
    else
        printError();

    //Test8: TAP_BAS testing
    delete compiled_info;
    compiled_info = new COMPILED_INFO();

    printf("Test 8: #1 ");
    inCompile->chrSourceCode = (char*)SOURCE_CODE_DEFAULT.c_str();
    inCompile->chrFileOut = (char*)"test8.out";
    inCompile->CompileMode = 2; //TAP_BAS
    inCompile->cInputIsFile = 'N';

    retCode = compileProc(inCompile, compiled_info);
    if (retCode != 0)
        printError(compiled_info->czErrMessage);
    //Test8(step2):
    printOK();
    printf("        #2 ");
    inCompile->cInputIsFile = 'Y';
    sprintf(inCompile->chrFileIn, "%s\\Test files\\test7_label_equ.asm", AppDir);
    retCode = compileProc(inCompile, compiled_info);
    if (retCode != 0)
        printError(compiled_info->czErrMessage);
    else
        printOK();

    remove("test8.out"); //delete file

	return 0;
}

static bool CheckRef(const char* i_refFileName, char* i_binBytes, int i_codeSize )
{
    streampos fileSize;
    ifstream refFile(i_refFileName, ios::in | ios::binary | ios::ate);
    if (refFile.is_open())
    {
        fileSize = refFile.tellg();
        char * fileContent = new char[(unsigned int)fileSize];
        refFile.seekg(0, ios::beg);
        refFile.read(fileContent, fileSize);
        refFile.close();

        if (i_codeSize >= 0)
        {
            //do not check code for tests expected to finish with an error
            if (i_codeSize != (long)fileSize-2) //code size is always REAL compiled code size(without first 2 bytes which is mem address)
                return false;
        }
        if (memcmp(fileContent, i_binBytes, (size_t)fileSize))
            return false;
    }
    else
    {
        printf("Missing ref file: %s\n", i_refFileName);
        return false;
    }

    return true;
}

static void printOK()
{
	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	printf("OK\n");
    SetConsoleTextAttribute(hStdout, infoCon.wAttributes);
}
/*static void printOK(const char* i_string)
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
    cout << i_string << std::endl;
    SetConsoleTextAttribute(hStdout, infoCon.wAttributes);
}
*/
static void printError()
{
	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
	cout << "ERROR!" << std::endl;
    SetConsoleTextAttribute(hStdout, infoCon.wAttributes);
}
static void printError(const char* i_errMessage)
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
	printf("%s\n", i_errMessage);
    SetConsoleTextAttribute(hStdout, infoCon.wAttributes);
}

static void SaveConsoleInfo()
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

    if (!GetConsoleScreenBufferInfo(hStdout, &infoCon))
    {
        printError("Console info get error !\n\n");
    }
}
